/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "../File.h"
#include <memory>
#include <vector>
#include <string>
#include "../FileSystem.h"
#include <FBase.h>
#include <FIo.h>

//#ifdef DEBUG
//#define FILE_DEBUG
//#endif

namespace liba
{

namespace filesystem
{

class FileStats
{
public:
	FileStats()
		: totalFilesOpened(0)
	{
	}

	void FileOpened(const std::string& name)
	{
		AppLog("File '%s' opened", name.c_str());
		++totalFilesOpened;

		for (List::const_iterator i = openedFiles.begin(); i != openedFiles.end(); ++i)
		{
			if (*i == name)
			{
				AppLog("Opened same file '%s' more than once", name.c_str());
				break;
			}
		}

		openedFiles.push_back(name);

		DumpStats();
	}

	void FileClosed(const std::string& name)
	{
		AppLog("File '%s' closed", name.c_str());

		bool foundFile = false;

		for (List::iterator i = openedFiles.begin(); i != openedFiles.end(); ++i)
		{
			if (*i == name)
			{
				openedFiles.erase(i);
				foundFile = true;
				break;
			}
		}

		DumpStats();
	}

	void DumpStats()
	{
		AppLog("Since the universe was born we've opened %d files", totalFilesOpened);
		AppLog("At the present moment we are holding %d file handles", openedFiles.size());
	}
private:
	typedef std::vector<std::string> List;
	List openedFiles;
	size_t totalFilesOpened;
};

#ifdef FILE_DEBUG

FileStats g_fileStats;

#endif

void FileOpened(const std::string& name)
{
#ifdef FILE_DEBUG
	g_fileStats.FileOpened(name);
#endif
}

void FileClosed(const std::string& name)
{
#ifdef FILE_DEBUG
	g_fileStats.FileClosed(name);
#endif
}

struct File::Impl
{
	std::string name;
	static const size_t READ_BUFFER_SIZE = 8192;

	char readBuffer[READ_BUFFER_SIZE];
	int readBufferPos;
	int readBufferLength;

	std::auto_ptr<Osp::Io::File> handle;
};

File::File(const filesystem::String & name, Mode mode)
	:	impl(new Impl())
{
	impl->readBufferPos = 0;
	impl->readBufferLength = -1;
	Osp::Base::String path(name.c_str());
	const char * access = mode == READ ? "r" : mode == WRITE ? "w" : "a";
	Osp::Base::String badaMode(access);

	if (mode == READ && !Osp::Io::File::IsFileExist(path))
		return;

	AppLogDebug("Opening file '%s' in mode '%s'", name.c_str(), access);

	{
		std::auto_ptr<Osp::Io::File> localFile(new Osp::Io::File());
		bool createDirs = (mode == WRITE) || (mode == APPEND);

		result r = localFile->Construct(path, access, createDirs);

		if (r == E_SUCCESS)
		{
			impl->name = name.c_str();
			impl->handle = localFile;
			FileOpened(impl->name);
		}
		else
			AppLogException("Failed to open file '%S' mode = %S - %s (createDirs = %s)", path.GetPointer(), badaMode.GetPointer(), GetErrorMessage(r), createDirs ? "true" : "false");
	}
}

bool File::is_valid()const
{
	return impl->handle.get() != 0;
}

File::~File()
{
	if (is_valid())
		FileClosed(impl->name);

	delete impl; impl = 0;
}

size_t File::write(const void * data, size_t count)
{
	if(!is_valid())
		return 0;

	result r = impl->handle->Write(data, count);
	if (r == E_SUCCESS)
	{
		return count;
	}
	else
	{
		AppLogException("Failed to write data to a file: '%s'", GetErrorMessage(r));

		return 0;
	}
}

size_t File::read(void * data, size_t count)
{
	if(!is_valid())
		return 0;

	// Prepare first buffer
	if (impl->readBufferLength < 0 || impl->readBufferPos == impl->readBufferLength)
	{
		impl->readBufferLength = impl->handle->Read(impl->readBuffer, Impl::READ_BUFFER_SIZE);
		impl->readBufferPos = 0;
	}

	char* cdata = reinterpret_cast<char*>(data);

	while (count > 0 && impl->readBufferLength > 0)
	{
		size_t readBufferSize = impl->readBufferLength - impl->readBufferPos;
		size_t copySize = std::min(count, readBufferSize);

		memcpy(cdata, impl->readBuffer + impl->readBufferPos, copySize);
		count -= copySize;
		cdata += copySize;
		impl->readBufferPos += copySize;

		if (impl->readBufferPos >= Impl::READ_BUFFER_SIZE)
		{
			impl->readBufferLength = impl->handle->Read(impl->readBuffer, Impl::READ_BUFFER_SIZE);
			impl->readBufferPos = 0;
		}
	}

	return cdata - reinterpret_cast<char*>(data);
}

size_t File::skip(size_t count)
{
	if(!is_valid())
		return 0;

	size_t remainder = (impl->readBufferLength - impl->readBufferPos);
	size_t logicalSkip = std::min(remainder, count);
	impl->readBufferPos += logicalSkip;
	count -= logicalSkip;

	if (count > 0)
	{
		result r = impl->handle->Seek(Osp::Io::FILESEEKPOSITION_CURRENT, count);
		if (r == E_SUCCESS)
		{
			impl->readBufferLength = -1;
			return count;
		}

		AppLogException("Failed to skip %d bytes: '%s'", count, GetErrorMessage(r));
	}

	return 0;
}

size_t File::get_size()const
{
	if(!is_valid())
		return 0;

	size_t currentPos = impl->handle->Tell();
	result r = GetLastResult();
	if (r != E_SUCCESS)
	{
		AppLogException("Failed to get file pointer: %s", GetErrorMessage(r));
		return 0;
	}

	r = impl->handle->Seek(Osp::Io::FILESEEKPOSITION_END, 0);
	if (r != E_SUCCESS)
	{
		AppLogException("Failed to seek to the end of file: %s", GetErrorMessage(r));
		return 0;
	}

	size_t size = impl->handle->Tell();
	r = GetLastResult();
	if (r != E_SUCCESS)
	{
		AppLogException("Failed to get file size: %s", GetErrorMessage(r));
		return 0;
	}

	r = impl->handle->Seek(Osp::Io::FILESEEKPOSITION_BEGIN, currentPos);
	if (r != E_SUCCESS)
	{
		AppLogException("Failed to seek back: %s", GetErrorMessage(r));
		return 0;
	}

	return size;
}

size_t File::get_position()const
{
	if(!is_valid())
		return 0;

	size_t pos = impl->handle->Tell();
	result r = GetLastResult();
	if (r != E_SUCCESS)
	{
		AppLogException("Failed to get file pointer: %s", GetErrorMessage(r));
		return 0;
	}

	pos -= impl->readBufferLength - impl->readBufferPos;
	return pos;
}

void File::reset(size_t position)
{
	if(!is_valid())
		return;

	result r = impl->handle->Seek(Osp::Io::FILESEEKPOSITION_BEGIN, position);
	impl->readBufferLength = -1;
	if (r != E_SUCCESS)
		AppLogException("Failed to seek to position %d: %s", position, GetErrorMessage(r));
}

void File::flush()
{
	if(!is_valid())
		return;

	result r = impl->handle->Flush();
	if (r != E_SUCCESS)
	{
		AppLogException("Failed to flush file");
	}
}

}//namespace filesystem

}//namespace liba

